package com.openNetty.server.nettyclient;


import com.openNetty.common.bean.ComRequest;
import com.openNetty.common.bean.HttpAck;
import com.openNetty.common.bean.HttpInfo;
import com.openNetty.common.bean.SendMsgFuture;
import com.openNetty.common.system.LoggerUtil;
import com.openNetty.common.utils.CRC8;
import com.openNetty.server.main.Global;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;

import java.io.*;

import static com.openNetty.common.bean.Command.GET_BREAK_BACKFILE;
import static com.openNetty.common.bean.HttpInfo.*;
import static com.openNetty.common.bean.MainContents.ROOT;
import static com.openNetty.common.utils.EncryptUtil.DESedeDecByte;
import static com.openNetty.common.utils.EncryptUtil.getKey;
import static com.openNetty.common.utils.HttpMsgInit.setHttpMsg;
import static com.openNetty.common.utils.MD5Util.getFileMD5;

/**
 * 处理NETTY消息接受指定类
 * 为了适应内外网，端口转发等代理设置。可以通过NGINX转发
 * 所有消息都封装成HTTP的格式，同时对HTTP进行自定义协议头设置。
 *
 * @author zhenggm
 * @create 2017-08-21 下午 4:29
 */
public class SimpleClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    private static LoggerUtil logger = LoggerUtil.getLogger(SimpleClientHandler.class);
    private boolean first = true;
    private boolean flag = false;

    private FileOutputStream fos;
    private SendMsgFuture future;
    private ComRequest comRequest;

    private static String encFilePath;
    private static String fileName;

    private int filesize = 0;
    private int position = 0;
    private int count = 0;
    private String CRC;
    private HttpAck httpAck = new HttpAck();
    private String key;
    /*
    是否是第一次创建文件
     */
    private boolean fileContext;

    private boolean finish = false;

    public SimpleClientHandler(SendMsgFuture future, ComRequest comRequest) {
        this.future = future;
        this.comRequest = comRequest;
        this.fileName = Global.properties.getProperty("back.filename");
    }

    /*
     * channelAction
     *
     * 当客户端主动链接服务端的链接后，这个通道就是活跃的了。也就是客户端与服务端建立了通信通道并且可以传输数据
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        first = true;
        logger.debug(ctx.channel().localAddress().toString() + " channelActive");
    }

    /**
     * 对消息自定义校验位进行校验有效性
     * @param ctx
     * @param response
     * @param buf
     * @return
     */
    private byte[] checkHttpMsg(ChannelHandlerContext ctx, FullHttpResponse response, ByteBuf buf) {
        try {
            // 校验位和对称秘钥
            CRC = response.headers().get(HttpInfo.CRC);
            key = response.headers().get(KEY);
            // logger.debug(CRC);
            byte[] req = new byte[buf.readableBytes()];
            buf.readBytes(req);
            // 检测秘钥是否为空
            if (key != null && !"".equals(key) && response.headers().get(MEMCOUNT) != null && response.headers().get(FILEPOSITION) != null) {
                // 获取秘钥解密
                req = DESedeDecByte(Global.keyMap.get(key), req);
                if (!Integer.toHexString(0x00ff & CRC8.calcCrc8(req)).equals(CRC)) {
                    logger.debug("块文件校验错误，稍后进行重连。");
                    Thread.sleep(Long.parseLong(Global.properties.getProperty("back.breaktime")));
                    // 如果校验不通过，返回上一次的位置重新发送。
                    httpAck.setPostition(position);
                    httpAck.setCount(count);
                    httpAck.setKey(getKey(Global.keyMap));
                    httpAck.setPass(Global.keyMap.get(httpAck.getKey()));
                    httpAck.setFileName(fileName);
                    comRequest.setMessage(GET_BREAK_BACKFILE);
                    comRequest.setHttpAck(httpAck);
                    ctx.writeAndFlush(setHttpMsg(comRequest));
                } else {
                    flag = true;
                }
            } else {
                logger.debug("数据包校验错误，稍后进行重连。");
                Thread.sleep(Long.parseLong(Global.properties.getProperty("back.breaktime")));
                httpAck.setPostition(position);
                httpAck.setCount(count);
                httpAck.setKey(getKey(Global.keyMap));
                httpAck.setPass(Global.keyMap.get(httpAck.getKey()));
                httpAck.setFileName(fileName);
                comRequest.setMessage(GET_BREAK_BACKFILE);
                comRequest.setHttpAck(httpAck);
                ctx.writeAndFlush(setHttpMsg(comRequest));
            }
            return req;
        } catch (Exception e) {
            logger.error(e);

        }
        return null;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        try {
            String path = Global.properties.getProperty(ROOT);
            if (msg instanceof FullHttpResponse) {
                FullHttpResponse response = (FullHttpResponse) msg;
                ByteBuf buf = response.content();
                // 结束确认帧
                byte[] req = checkHttpMsg(ctx, response, buf);
                String result = new String(req, "UTF-8");
                if (flag) {
                    switch (result) {
                        case SUCCESS:
                            finish = true;
                            logger.debug("文件接收完成，文件总大小为：" + filesize / 1000 + "KB");
                            String MD5 = response.headers().get(HttpInfo.MD5);
                            String mdfive = getFileMD5(encFilePath);
                            if (MD5.equals(mdfive)) {
                                logger.info("文件校验成功");
                                future.success("文件下载完成");
                                this.channelInactive(ctx);
                            } else {
                                logger.error("文件校验失败，MD5值不一致，重新下载");
                                future.fail("文件下载失败，重新下载", "文件校验错误", encFilePath);
                                this.channelInactive(ctx);
                            }
                            fileContext = false;
                            break;
                        default:
                            fileContext = true;
                            break;
                    }
                    if (fileContext) {
                        // 第一次接收信息时候创建文件
                        if (first) {
                            first = false;
                            future.setFilepath(fileName);
                            // 将文件名进行存储
                            httpAck.setFileName(fileName);
                            encFilePath = path + fileName;
                            // 创建文件
                            try {
                                logger.debug("create file :" + encFilePath);
                                fos = new FileOutputStream(encFilePath, true);
                            } catch (IOException e) {
                                e.printStackTrace();
                                logger.error(e.getMessage());
                            }
                        }
                        // 开始处理文件信息
                        count = Integer.parseInt(response.headers().get(MEMCOUNT));
                        position = Integer.parseInt(response.headers().get(FILEPOSITION));

                        filesize += req.length;
                        if (filesize % 10000 == 0) {
                            logger.debug("本次接收内容长度：" + req.length + "  已接收总长度为：" + filesize);
                        }
                        try {
                            fos.write(req, 0, req.length);
                            buf.release();
                        } catch (IOException e) {
                            e.printStackTrace();
                            logger.error(e.getMessage());
                            this.exceptionCaught(ctx, e.getCause());
                        }
                        // 设定文件断点位置
                        httpAck.setPostition(position);
                        httpAck.setCount(count);
                        httpAck.setKey(getKey(Global.keyMap));
                        httpAck.setPass(Global.keyMap.get(httpAck.getKey()));
                        comRequest.setMessage(ACK);
                        comRequest.setHttpAck(httpAck);
                        ctx.writeAndFlush(setHttpMsg(comRequest));
                    }
                }
            } else {
                super.channelRead(ctx, msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e.getMessage(), e.getCause());
            exceptionCaught(ctx, e.getCause());
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
    }

    /**
     * 异常捕获，通常用于文件下载中断的情况。比如网络断开导致的中断。
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //cause.printStackTrace();
        logger.error(cause.getMessage());
        logger.info("文件下载中断，进入断点续传列表，30秒后尝试重新连接。当前下载位置##count：" + count + "##position:" + position);

        // 关闭流
        future.fail("文件下载中断，进入断点列表", cause.getMessage(), encFilePath);
        if (httpAck.getFileName() != null) {
            Global.breakFile.put(httpAck.getFileName(), httpAck);
        }
        ctx.close();
    }

    /*
     * channelReadComplete
     *
     * 在通道读取完成后会在这个方法里通知，对应可以做刷新操作
     * ctx.flush()
     *
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    /*
     * channelInactive
     *
     * 当客户端主动断开服务端的链接后，这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据
     *
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {

        if (finish) {
            logger.debug(ctx.channel().localAddress().toString() + " channelInactive");
            if (fos != null) {
                fos.close();
            }
            ctx.close();
            System.gc();
            first = false;
        } else {
            exceptionCaught(ctx, new Throwable("通道中断"));
        }
    }

}
